<?php

declare(strict_types=1);

namespace Hyperf\Mongodb;

use Hyperf\Mongodb\Exception\MongoDBException;
use Hyperf\Mongodb\Pool\PoolFactory;
use Hyperf\Utils\Context;

/**
 * Class MongoDb
 * @package Hyperf\Mongodb
 */
class MongoDb
{
    /**
     * @var PoolFactory
     */
    protected $factory;

    /**
     * @var string
     */
    protected $poolName = 'default';

    public function __construct(PoolFactory $factory)
    {
        $this->factory = $factory;
    }

    /**
     * 返回满足filer的全部数据
     * @param string $namespace
     * @param array  $filter
     * @param array  $options
     * @return array
     * @throws MongoDBException
     */
    public function fetchAll(string $namespace, array $filter = [], array $options = []): array
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->executeQueryAll($namespace, $filter, $options);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 返回满足filer的分页数据
     * @param string $namespace
     * @param int    $limit
     * @param int    $currentPage
     * @param array  $filter
     * @param array  $options
     * @return array
     * @throws MongoDBException
     */
    public function fetchPagination(
        string $namespace,
        int $limit,
        int $currentPage,
        array $filter = [],
        array $options = []
    ): array {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->execQueryPagination($namespace, $limit, $currentPage, $filter, $options);
        } catch (\Exception  $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 批量插入
     * @param string $namespace
     * @param array  $data
     * @return bool|string
     * @throws MongoDBException
     */
    public function insertAll(string $namespace, array $data)
    {
        if (count($data) == count($data, 1)) {
            throw new  MongoDBException('data is can only be a two-dimensional array');
        }
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->insertAll($namespace, $data);
        } catch (MongoDBException $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 数据插入数据库
     * @param string $namespace
     * @param array  $data
     * @return bool|mixed
     * @throws MongoDBException
     */
    public function insert(string $namespace, array $data = [])
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->insert($namespace, $data);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 更新数据满足$filter的行的信息成$newObject
     * @param string $namespace 集合名
     * @param array  $filter    查询条件
     * @param array  $newObj    要更新的文档记录
     * @param string $option    操作选项,可选择项如下；
     *
     * 'set'：只修改指定的字段（默认值,如果这个键不存在，则创建它。存在则更新）.
     * 示例: update('user', array('name'=>'mongo'), array('id'=>10));
     * 类似: update user set name='mongo' where id=10;
     *
     * 'inc'：将指定的字段累加/减(如果值为负数则是相减,不存在键则创建。字段类型一定要是数字)
     * 示例：update('user', array('num'=>1), array('id'=>10), 'inc');
     * 类似: update user set num=num+1 where id=10;
     *
     * 'push'：将文档添加到指定键中（数组），如果键不存在则会自动创建，存在则添加到该键的尾端。
     * 示例：update('user', array('comm'=>array('commid'=>1,'title'=>'title1')), array('id'=>1), 'push');
     * 解说：为 id=1 的记录添加一个 comm 的评论字段，该字段对应一个 array('commid'=>1,'title'=>'title1') 的新文档。
     *
     * 'pop':将指定键中的文档删除（数组）
     * 示例：update('user', array('comm'=>array('commid'=>1)), array('id'=>1), 'pop');
     * 解说：删除 id=1 的记录中 comm 对应的文档集合中 'commid'=>1 对应的文档.
     *
     * 'unset':在文档中删除指定的键
     * 示例：update('user', array('name'=>1), array('id'=>1), 'unset');
     * 解说: 将 user 集合中将 id=1 对应的文档中的 name 字段删除
     *
     * 'pull':删除文档中匹配其值的键
     * 示例：update('user', array('name'=>'youname'), array('id'=>1), 'pull');
     * 解说：将 user 集合中将 id=1 对应的文档中的 name='youname' 的字段删除
     *
     * 'addToSet':如果值不存在就添加（避免重复添加）
     * 示例：update('user', array('names'=>'youname'), array('id'=>1), 'addToSet');
     * 解说：向 user 集合中 id=1 对应的文档中的 names 字段添加 'youname' 这个值(不存在时才添加)
     *
     * 'replace'：用 $newDoc 新文档替换 $query 所找到的文档
     * 示例：update('user', array('newid'=>1,'newnames'=>'name1'), array('id'=>1), 'replace');
     * 解说：将 user 集合中 id=1 对应的文档用 array('newid'=>1,'newnames'=>'name1') 的新文档替换
     *
     * @return bool
     * @throws MongoDBException
     */
    public function updateField(string $namespace, array $filter = [], array $newObj = [], $option = 'set'): bool
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->updateField($namespace, $filter, $newObj, $option);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 更新数据满足$filter的行的信息成$newObject
     * @param string $namespace
     * @param array  $filter
     * @param array  $newObj
     * @return bool
     * @throws MongoDBException
     */
    public function updateRow(string $namespace, array $filter = [], array $newObj = []): bool
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->updateRow($namespace, $filter, $newObj);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 只更新数据满足$filter的行的列信息中在$newObject中出现过的字段
     * @param string $namespace
     * @param array  $filter
     * @param array  $newObj
     * @return bool
     * @throws MongoDBException
     */
    public function updateColumn(string $namespace, array $filter = [], array $newObj = []): bool
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->updateColumn($namespace, $filter, $newObj);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 删除满足条件的数据，默认只删除匹配条件的第一条记录，如果要删除多条$limit=true
     * @param string $namespace
     * @param array  $filter
     * @param bool   $limit
     * @return bool
     * @throws MongoDBException
     */
    public function delete(string $namespace, array $filter = [], bool $limit = false): bool
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->delete($namespace, $filter, $limit);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    /**
     * 返回collection中满足条件的数量
     * @param string $namespace
     * @param array  $filter
     * @return bool
     * @throws MongoDBException
     */
    public function count(string $namespace, array $filter = [])
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->count($namespace, $filter);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }


    /**
     * 聚合查询
     * @param string $namespace
     * @param array  $filter
     * @return bool
     * @throws MongoDBException
     * @throws \MongoDB\Driver\Exception\Exception
     */
    public function command(string $namespace, array $filter = [])
    {
        try {
            /**
             * @var $collection MongoDBConnection
             */
            $collection = $this->getConnection();
            return $collection->command($namespace, $filter);
        } catch (\Exception $e) {
            throw new MongoDBException($e->getFile() . $e->getLine() . $e->getMessage());
        }
    }

    private function getConnection()
    {
        $connection           = null;
        $hasContextConnection = Context::has($this->getContextKey());
        if ($hasContextConnection) {
            $connection = Context::get($this->getContextKey());
        }
        if (!$connection instanceof MongoDbConnection) {
            $pool       = $this->factory->getPool($this->poolName);
            $connection = $pool->get()->getConnection();
        }
        return $connection;
    }

    /**
     * The key to identify the connection object in coroutine context.
     */
    private function getContextKey(): string
    {
        return sprintf('mongodb.connection.%s', $this->poolName);
    }

}